package br.com.programacao.concorrente.problema;

import br.com.programacao.concorrente.auxiliar.Log;
import br.com.programacao.concorrente.auxiliar.Constantes;
import java.io.File;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.Semaphore;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A classe Consumidor é responsável por ser a base da Thread e consumir o
 * buffer. Ela implementa a interface Runnable e será instanciada em Thread
 */
public class Consumidor implements Runnable {

    private final Buffer buffer;
    private final int execution;
    private final Semaphore sem;
    private final Semaphore mutex;

    /**
     * Para instanciar um Consumidor, é necessário informar qual é o numero da
     * execução e em qual buffer ele irá consumir (referência do buffer).
     *
     * @param buffer
     * @param execution
     * @param sem
     * @param mutex
     *
     */
    public Consumidor(Buffer buffer, int execution, Semaphore sem, Semaphore mutex) {
        this.buffer = buffer;
        this.execution = execution;
        this.sem = sem;
        this.mutex = mutex;
    }

    /**
     * Metodo que será executado na Thread
     */
    @Override
    public synchronized void run() {
        try {
            //Pega o inicio do processamento
            long timeInitThread = Constantes.getTimeMillis();
            int qtdTheads = 0;
            final String inicioThread = "Inicio da thread consumidora" + " " + Thread.currentThread().getName() + " "
                    + " Execução:" + this.execution + " "
                    + Calendar.getInstance().getTime();

            //Grava no log o inicio
            Log.getLogger(this.getClass()).info(inicioThread);

            //Para a primeira chave do buffer. Caso ela não exista, não é executado o
            //loop de execução.
            String next = buffer.getNextKey();
            while ((Constantes.getTimeMillis() - Constantes.INSTANT_INIT) < Constantes.TIME_EXECUTION) {
                if (next != null) {
                    try {
                        qtdTheads++;
                        long inicio = Constantes.getTimeMillis();
                        Date dtInicio = Calendar.getInstance().getTime();

                        //requisita um para o semaforo pegar
                        sem.acquire(1);
                        mutex.acquire();
                        File file = buffer.getFile(next);
                        String id = gravaLogInicio(file, dtInicio);
                        Thread.sleep(Constantes.INTERVAL_THREAD);
                        mutex.release();
                        //libera o semaforo.

                        gravaLogFim(id, file, inicio);

                    } catch (InterruptedException | NullPointerException ex) {
                        Log.getLogger(this.getClass()).error(ex);
                    } finally {
                        next = buffer.getNextKey();
                    }
                } else {
                    next = buffer.getNextKey();
                }
            }
            long timeEndThread = Constantes.getTimeMillis();
            final String terminoThread = "Termino da thread" + " " + Thread.currentThread().getName()
                    + " Execução:" + this.execution + " "
                    + " " + Calendar.getInstance().getTime();
            Log.getLogger(this.getClass()).info(terminoThread);
            Log.includeLogFileExecution(this.execution, (timeEndThread - timeInitThread), qtdTheads);

        } catch (InterruptedException ex) {
            Logger.getLogger(Consumidor.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Grava no log o fim do processamento do arquivo
     *
     * @param file
     * @param dtInicio
     * @return id do pedido
     * @throws NullPointerException
     */
    private String gravaLogInicio(File file, Date dtInicio) throws NullPointerException {
        String id = String.valueOf(Thread.currentThread().getName());
        String inicioLog = "Thread Consumidora #" + id + " Pedido #" + file.getName() + " Inicio:" + dtInicio;
        Log.getLogger(this.getClass()).debug(inicioLog);
        return id;
    }

    /**
     * Grava no log o fim do processamento do arquivo
     *
     * @param id
     * @param file
     * @param inicio
     * @throws NullPointerException
     */
    private void gravaLogFim(String id, File file, long inicio) throws NullPointerException {
        String fimLog = "Thread Consumidora #" + id + " Pedido #" + file.getName() + " Termino:" + Calendar.getInstance().getTime();
        Log.getLogger(this.getClass()).debug(fimLog);
        long fim = Constantes.getTimeMillis();
        Log.includeLogFileThread(this.execution, id, file.getName(), (fim - inicio), "Consumidora");
    }
}
